/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package vue.composants;

import java.awt.AlphaComposite;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.color.ColorSpace;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.image.BufferedImage;
import java.awt.image.ColorConvertOp;
import javax.swing.JPanel;
import main.Outils;
import modele.ZoneImage;
import vue.EcranRetouche;

/**
 *
 * @author Fabien Muguet
 */
public class EcranRetoucheImage extends JPanel implements MouseListener, MouseMotionListener {

    private EcranRetouche ecran;
    private Dimension tailleCadre;

    private Rectangle rognage;

    private double ratioRognage;

    private boolean dragging;
    private Point p;


    public EcranRetoucheImage(EcranRetouche ecran, Dimension tailleCadre) {
        this.ecran = ecran;
        this.tailleCadre = tailleCadre;

        rognage = new Rectangle();

        ZoneImage zImage = ecran.getZone();
        ratioRognage = (double)zImage.getWidth() / (double)zImage.getHeight();

        // Pour rognage
        dragging = false;
        p = new Point();
        addMouseListener(this);
        addMouseMotionListener(this);

        // Par défaut on rogne au centre
        rognage.width = tailleCadre.width / 2;
        rognage.height = (int)Math.round((double)rognage.width / ratioRognage);
        rognage.x = (tailleCadre.width - rognage.width) / 2;
        rognage.y = (tailleCadre.height - rognage.height) / 2;

        // Curseur indiquant qu'il est possible de rogner
        setCursor(Cursor.getPredefinedCursor( Cursor.CROSSHAIR_CURSOR) );

        // On force la taille du panel
        setSize(tailleCadre);
        setPreferredSize(tailleCadre);
    }



    /**
     * Génère une BufferedImage avec tous les effets.
     *
     * @return Une BefferedImage avec les effets.
     */
    private BufferedImage genererImageAvecEffets(boolean redimensionner) {

        BufferedImage maitre;
        BufferedImage aModifier;
        BufferedImage tampon;
        BufferedImage copie;


        // On ne va appliquer qu'une partie des effets si besoin
        boolean tousLesEffets = !dragging && !ecran.isSlidding();

        // Infos sur l'image d'origine
        int wReel = ecran.getImage().getImage().getWidth();
        int hReel = ecran.getImage().getImage().getHeight();
        int dReel = (int)Math.sqrt((wReel*wReel) + (hReel*hReel));
        double r = (double)wReel / (double)hReel;
        boolean paysage = wReel > hReel;

        // Coté du carré dans lequel l'image doit rentré malgré les rotations
        int c;
        if( redimensionner )
            c = tailleCadre.width;
        else
            c = dReel;

        // Facteur de redimensionnement de l'image
        double facteur;
        if( redimensionner )
            facteur = (double)c / (double)dReel;
        else
            facteur = 1.0;


        // On récupère l'image à modifier
        aModifier = ecran.getImage().getImage();


        // Ajuste l'image aux bonnes dimensions avant d'appliquer les effets
        copie = Outils.homothetie(aModifier, facteur);
        Graphics2D gCopie = (Graphics2D)copie.getGraphics();

        
        // On applique le noir et blanc ou le sépia
        if( ecran.isNoirEtBlanc() && tousLesEffets ) {
            ColorConvertOp op = new ColorConvertOp(
                    ColorSpace.getInstance(ColorSpace.CS_GRAY),
                    null);
            copie = op.filter(copie, null);
        } else if( ecran.isSepia() && tousLesEffets ) {
            try {
                copie = Outils.appliquerSepia(copie, 10);
            }
            catch(Exception ex) {
                System.out.println("Impossible d'appliquer le sépia.");
                ex.printStackTrace();
            }
        }


        // On applique la luminosité
        if( ecran.getLuminosite() >= -100 &&  ecran.getLuminosite() < 0 && !dragging ) {
            double luminosite = ((double)ecran.getLuminosite()*(double)(-1)) / (double)100;
            gCopie.setColor(Color.BLACK);
            gCopie.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, (float)luminosite ));
            gCopie.fillRect(0, 0, aModifier.getWidth(), aModifier.getHeight());
        } else if( ecran.getLuminosite() > 0 &&  ecran.getLuminosite() <= 100 && !dragging ) {
            double luminosite = (double)ecran.getLuminosite() / (double)100;
            gCopie.setColor(Color.WHITE);
            gCopie.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, (float)luminosite ));
            gCopie.fillRect(0, 0, aModifier.getWidth(), aModifier.getHeight());
        }


        // On applique le contraste
        if( ecran.getContraste() != 0 && !dragging ) {

//            float [] accentuation = { 0f, -1f, 0f,
//                          1f, 5f, -1f,
//                          0f, -1f, 0f};
//
//            Kernel k = new Kernel(3, 3, accentuation);
//            ConvolveOp op = new ConvolveOp(k);
//            copie = op.filter(copie, null);

        }


        // Création de l'image pour rotation
        tampon = new BufferedImage(c, c, BufferedImage.TYPE_3BYTE_BGR);
        Graphics2D gTampon = (Graphics2D)tampon.getGraphics();

        // On dessine l'image au centre dans le carré
        int xCentre = (c - copie.getWidth()) / 2;
        int yCentre = (c - copie.getHeight()) / 2;
        gTampon.drawImage(copie, xCentre, yCentre, null);
        
        // On libère l'espace
        copie = null;
        gCopie = null;

        // On effectue une rotation de l'image ainsi construite
        tampon = Outils.rotate(tampon, ecran.getRotation());

        // C'est dans l'image maitre que l'on va tout dessiner
        maitre = new BufferedImage(c, c, BufferedImage.TYPE_3BYTE_BGR);
        Graphics g = maitre.getGraphics();

        // On dessine l'image transformée
        g.drawImage(tampon, 0, 0, null);

        // On libère l'espace
        tampon = null;
        gTampon = null;

        // On retourne l'image générée
        return maitre;
    }



    /**
     * Dessin du panel.
     *
     * @param g Contexte graphique
     */
    public void paintComponent(Graphics g) {

        Graphics2D g2d = (Graphics2D)g;

        // On génère l'image
        BufferedImage generee = genererImageAvecEffets(true);

        // Cadre délimitant la zone de travail
        g.setColor( new Color(200,200,230) );
        g.fillRect(0, 0, tailleCadre.width, tailleCadre.height);
        g.setColor( new Color(100,100,130) );
        g.drawRect(0, 0, tailleCadre.width, tailleCadre.height);


        // On dessine l'image transformée
        g.drawImage(generee, 0, 0, null);


        // On dessine la zone de rogage
        float opacite = 0.75f;
        float epaisseur = 2;
        float[] pointilles = {10,5};
        Color couleur = new Color(235,0,0);

        g2d.setStroke( new BasicStroke(
            epaisseur,
            BasicStroke.CAP_BUTT,
            BasicStroke.JOIN_MITER,
            10.0f,
            pointilles,
            0
        ));
        g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, opacite ));
        g2d.setColor( couleur );
        g2d.drawRect(rognage.x, rognage.y, rognage.width, rognage.height);

    }

    /**
     * Retourne la zone rognée de l'image avec tous les effets appliqués.
     *
     * @return la zone rognée de l'image avec tous les effets appliqués.
     */
    public BufferedImage getImageAvecEffet() {

        // On génère l'image
        BufferedImage generee = genererImageAvecEffets(false);

        // On recalcule les coordonnées sans la réduction
        int wReel = ecran.getImage().getImage().getWidth();
        int hReel = ecran.getImage().getImage().getHeight();
        int dReel = (int)Math.sqrt((wReel*wReel) + (hReel*hReel));

        // Coté du carré dans lequel l'image doit rentré malgré les rotations
        int c = tailleCadre.width;

        // Facteur inverse de redimensionnement de l'image
        double facteur = (double)dReel / (double)c;

        //System.out.println(facteur);

        Rectangle selection = new Rectangle(rognage);
        selection.x *= facteur;
        selection.y *= facteur;
        selection.width *= facteur;
        selection.height *= facteur;
        
        // On récupère seulement la zone selectionnée
        generee = generee.getSubimage(selection.x, selection.y, selection.width, selection.height);

        // Débug
//        JLabel label = new JLabel(new ImageIcon(rognee));
//        JOptionPane.showMessageDialog(this, label, "clipped image",JOptionPane.PLAIN_MESSAGE);

        // On la retourne
        return generee;
    }


    /**
     * Clic sourie : début du drag.
     *
     * @param e Evennement
     */
    public void mousePressed(MouseEvent e) {
        p = e.getPoint();
        rognage = new Rectangle(p);
        dragging = true;
        ecran.redessinerImage();
    }

    /**
     * Relachement du clic sourie : fin du drag.
     *
     * @param e Evennement
     */
    public void mouseReleased(MouseEvent e) {
        dragging = false;
        ecran.redessinerImage();
    }

    /**
     * Un drag est en cours.
     *
     * @param e Evennement
     */
    public void mouseDragged(MouseEvent e) {
        if(dragging) {
            int w, h;
            Point p2 = e.getPoint();

            // Si dépassement du cadre, on ramène à l'extrême
            if(p2.x > tailleCadre.width)
                p2.x = tailleCadre.width;
            if(p2.y > tailleCadre.height)
                p2.y = tailleCadre.height;

            // Différence entre le point de départ et d'arrivée
            int dX = p2.x - p.x;
            int dY = p2.y - p.y;

            // Gestion des selection inversées
            dX *= dX < 0 ? -1 : 1;
            dY *= dY < 0 ? -1 : 1;

            // Gestion d'une selection au mauvais ratio
            w = dX;
            h = dY;
            double calcul = (double)dX / ratioRognage;
            if( dY != calcul ) {
                h = (int)calcul;
            }

            // Gestion d'une selection trop grande
            if( w > tailleCadre.width ) {
                w = tailleCadre.width;
                h = (int)Math.round( (double)dX / ratioRognage );
            } else if( h > tailleCadre.height ) {
                w = (int)Math.round( (double)dY * ratioRognage );
                h = tailleCadre.height;
            }

            // Gestion des selection inversées
            rognage.x = dX < 0 ? p2.x : p.x;
            rognage.y = dY < 0 ? p2.y : p.y;

            // On donne la taille au rectangle
            rognage.setSize(w, h);

            // On redessine à la volée
            ecran.redessinerImage();
        }
    }

    // Implémentation obligatoire
    public void mouseEntered(MouseEvent e) { }
    public void mouseExited(MouseEvent e) { }
    public void mouseClicked(MouseEvent e) { }
    public void mouseMoved(MouseEvent e) { }

}
